#pragma once

#include "Affects.h"

Affects::Affects(VarTable* vt,StmtTable *st,Modifies *m,Uses *u,CFG *c)
{
	myST = st;
	myVT = vt;
	myM = m;
	myU = u;
	myCFG = c;
};

void Affects::computeClosure()
{
	pair <unordered_multimap<INDEX,INDEX>::iterator, unordered_multimap<INDEX,INDEX>::iterator> ret;
	unordered_set<INDEX> accum;
	bool isChanged;
	int initSize;
	for(int i = 0;i<=myST->getSize();i++)
	{
		isChanged = true;
		accum.clear();

		pair <unordered_multimap<INDEX,INDEX>::iterator, unordered_multimap<INDEX,INDEX>::iterator> ret;
		ret = followsTableStmt.equal_range(i);
		for (unordered_multimap<INDEX,INDEX>::iterator it=ret.first; it!=ret.second; ++it)
		{
			accum.insert(it->second);
		}
		while(isChanged)
		{
			isChanged = false;
			initSize = accum.size();
			
			for (auto it = accum.begin(); it != accum.end(); ++it)
			{
				pair <unordered_multimap<INDEX,INDEX>::iterator, unordered_multimap<INDEX,INDEX>::iterator> ret;
				ret = followsTableStmt.equal_range(*it);
				for (unordered_multimap<INDEX,INDEX>::iterator it=ret.first; it!=ret.second; ++it)
				{
					accum.insert(it->second);
				}
			}
			if(initSize!=accum.size())
			{
				isChanged = true;
			}
		}
		for (auto it = accum.begin(); it != accum.end(); ++it)
		{
			followsTableStmtClosure.insert((std::make_pair<INDEX,INDEX>(i,*it)));
		}
	}
};

Schema Affects::getSynSyn(DESIGN_ENTITY de1, NAME de1Name, DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	attNames.push_back(de2Name);
	Schema x(2,attNames);
	if(de1Name!=de2Name)
	{
		for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
			if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt") && (myST->stmtTable.at(it->second) == de2 || de2 == "stmt")) {
				vector<INDEX> vect;
				vect.push_back(it->first);
				vect.push_back(it->second);
				x.insertTuple(vect);
			}
		}
		return x;
	}
	else
	{
		return x;
	}
};

Schema Affects::getSynIntClosure(DESIGN_ENTITY de1, NAME de1Name, INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	Schema x(1,attNames);

	//-----------------------------------------------------------------------

	set<INDEX> accum;
	bool isChanged;
	int initSize;
	
	isChanged = true;
	accum.clear();

	accum = getSynInt(de1,de1Name,para2).project(de1Name);

	while(isChanged)
	{
		isChanged = false;
		initSize = accum.size();
			
		for (auto it = accum.begin(); it != accum.end(); ++it)
		{
			set<INDEX> intermediate = getSynInt("stmt",de1Name,*it).project(de1Name);
			for (auto it2 = accum.begin(); it2 != accum.end(); ++it2)
			{
				accum.insert(*it2);
			}
		}

		if(initSize!=accum.size())
		{
			isChanged = true;
		}
	}

	for (auto it = accum.begin(); it != accum.end(); ++it)
	{
		vector<INDEX> vect;
		vect.push_back(*it);	
		x.insertTuple(vect);
	}
	return x;
};

Schema Affects::getSynInt(DESIGN_ENTITY de1, NAME de1Name, INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	Schema x(1,attNames);

	Schema nextResult = myCFG->getSynIntClosure(de1, de1Name, para2);
	set<INDEX> n = nextResult.project(de1Name);

	Schema usesResult = myU->getIntSyn(para2,"nombre");
	set<INDEX> u = usesResult.project("nombre");
	set<INDEX> localFinal;
	recurseFinal.clear();
	
	INDEX acMod;
		
	for (auto it = n.begin(); it != n.end(); ++it) 
	{
		if(myST->stmtTable.at(*it)=="assign")
		{
			
			Schema modResult = myM->getIntSyn(*it,"fra");
			set<INDEX> m = modResult.project("fra");
		
			for (auto it2 = m.begin(); it2 != m.end(); ++it2) 
			{
				acMod = *it2;
			}
			set<INDEX>::const_iterator got = u.find(acMod);	
			if(got==u.end())
			{
			}
			else
			{
				recursive(*it,para2,myVT->getVarName(acMod),-2,*it);
			}
		}
	}
	
	for (auto it = recurseFinal.begin(); it != recurseFinal.end(); ++it) 
	{
		if(*it!=para2)
		{
			vector<INDEX> vect;
			vect.push_back(*it);
			x.insertTuple(vect);
		}
	}
	//set<INDEX>::const_iterator got = useLastModSet.find(*it);

	//set<INDEX> useLastModSet = (myST->affectTable.at(para2));

	return x;
};

void Affects::recursive(INDEX hero,INDEX base,NAME modVar,INDEX calledFromLoop,INDEX original)
{
	if(hero==base)
	{
		recurseFinal.insert(original);
		return;
	}
	for (auto it2 = myCFG->forward.begin(); it2 != myCFG->forward.end(); ++it2) 
	{
		if(it2->first==hero)
		{
			if(    (it2->second!=-1)&& (it2->second!=calledFromLoop)        &&    (     (myST->stmtTable.at(it2->second)=="if"||myST->stmtTable.at(it2->second)=="while")    ||    (   (myST->stmtTable.at(it2->second)=="assign"||myST->stmtTable.at(it2->second)=="call")   &&     (base==it2->second||myM->getIntIdent(it2->second,modVar).numRows==0)    )))
			{
				INDEX localCalledFrom = -2;
				if(myST->stmtTable.at(hero)=="while")
				{
					localCalledFrom = hero;
				}
				else
				{
					localCalledFrom = calledFromLoop;
				}
				recursive(it2->second,base,modVar,localCalledFrom,original);
			}
		}
	}
	return;
};

Schema Affects::getSynUnd(DESIGN_ENTITY de1, NAME de1Name)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt")) {
			vector<INDEX> vect;
			vect.push_back(it->first);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getIntSyn(INDEX para1, DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de2Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if (it->first==para1 && (myST->stmtTable.at(it->second) == de2 || de2 == "stmt")) {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getIntInt(INDEX para1, INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if ((it->first==para1) && (it->second==para2)) {
			vector<INDEX> vect;
			vect.push_back(para1);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getIntUnd(INDEX para1)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if (it->first==para1) {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getUndInt(INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if ((it->second==para2)) {
			vector<INDEX> vect;
			vect.push_back(para2);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getUndSyn(DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de2Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		if (myST->stmtTable.at(it->second) == de2 || de2 == "stmt") {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getUndUnd()
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) 
	{
		vector<INDEX> vect;
		vect.push_back(it->second);
		x.insertTuple(vect);
	}
	return x;
};

Schema Affects::getSynSynClosure(DESIGN_ENTITY de1, NAME de1Name, DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	attNames.push_back(de2Name);
	Schema x(2,attNames);
	if(de1Name!=de2Name)
	{
		for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
			if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt") && (myST->stmtTable.at(it->second) == de2 || de2 == "stmt")) {
				vector<INDEX> vect;
				vect.push_back(it->first);
				vect.push_back(it->second);
				x.insertTuple(vect);
			}
		}
		return x;
	}
	else
	{
		return x;
	}
};

Schema Affects::getSynUndClosure(DESIGN_ENTITY de1, NAME de1Name)
{
	vector<NAME> attNames;
	attNames.push_back(de1Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if ((myST->stmtTable.at(it->first) == de1 || de1 == "stmt")) {
			vector<INDEX> vect;
			vect.push_back(it->first);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getIntSynClosure(INDEX para1, DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de2Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if (it->first==para1 && (myST->stmtTable.at(it->second) == de2 || de2 == "stmt")) {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getIntIntClosure(INDEX para1, INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if ((it->first==para1) && (it->second==para2)) {
			vector<INDEX> vect;
			vect.push_back(para1);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getIntUndClosure(INDEX para1)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if (it->first==para1) {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getUndIntClosure(INDEX para2)
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if ((it->second==para2)) {
			vector<INDEX> vect;
			vect.push_back(para2);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getUndSynClosure(DESIGN_ENTITY de2, NAME de2Name)
{
	vector<NAME> attNames;
	attNames.push_back(de2Name);
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) {
		if (myST->stmtTable.at(it->second) == de2 || de2 == "stmt") {
			vector<INDEX> vect;
			vect.push_back(it->second);
			x.insertTuple(vect);
		}
	}
	return x;
};

Schema Affects::getUndUndClosure()
{
	vector<NAME> attNames;
	attNames.push_back("_");
	Schema x(1,attNames);
	for (auto it = followsTableStmtClosure.begin(); it != followsTableStmtClosure.end(); ++it) 
	{
		vector<INDEX> vect;
		vect.push_back(it->second);
		x.insertTuple(vect);
	}
	return x;
};

void Affects::printAll() {
	cout << "Printing Parent Table" << endl;
	for (auto it = followsTableStmt.begin(); it != followsTableStmt.end(); ++it) {
		cout << it->first << " : " << it->second << endl;
	}
};